/*
    SpawnSurance - Minecraft Plugin for casualty insurance of inventory contents
    Copyright (C) 2012 Ernest Pazera

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
package com.pdg.mcplugin.spawnsurance;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;

import net.milkbowl.vault.economy.Economy;

import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;

public class DataProvider {
	//plugin
	private SpawnSurance plugin;
	public SpawnSurance getPlugin(){
		return plugin;
	}
	
	//logger
	public Logger getLogger(){
		return getPlugin().getLogger();
	}
	
	//ensure data folder exists
	public void ensureDataFolder(){
		//find the folder
		File folder = getPlugin().getDataFolder();
		//does it exist?
		if(!folder.exists()){
			//it doesnt exist
			//try to make it
			if(folder.mkdir()){
				//success
				getLogger().info(Constants.messageCreatedDataFolder);
			}else{
				//failure
				getLogger().severe(Constants.errorCouldNotCreateDataFolder);
			}
		}
	}
	
	//connection
	private Connection connection;
	public Connection getConnection(){
		//if no connection exists
		if(connection==null){
			//make sure the data folder exists
			ensureDataFolder();
			try{
				//look for sqlite
				Class.forName(Constants.classNameSQLite);
			} catch(Exception e){
				//error occurred, so no sql lite!
				getLogger().info(Constants.errorCouldNotLoadSQLite);
				e.printStackTrace();
				return null;
			}
			try{
				//create a connection
				connection = DriverManager.getConnection(String.format(Constants.connectionString,getPlugin().getDataFolder().toString()));
			}catch(Exception e){
				//error occurred, so no connection
				getLogger().info(Constants.errorCouldNotConnectToDatabase);
				e.printStackTrace();
				connection = null;
				return null;
			}
		}
		//yay! a connection
		return connection;
	}
	
	//ensure that the inventory table exists
	private void ensureInventoryTable(){
		try{
			//create a statement to check for inventory table
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(Constants.dbCheckForInventoryTable);
			
			//if not found...
			if(!result.next()){
				//..execute statement to create table
				statement = getConnection().createStatement();
				statement.execute(Constants.dbCreateInventoryTable);
			}
		}catch(Exception e){
			//an error has occurred
			getLogger().info(Constants.errorCouldNotCreateInventoryTable);
			e.printStackTrace();
		}
	}
	
	//ensure that the inventory enchantments table exists
	private void ensureInventoryEnchantmentsTable(){
		try{
			//statement to check for table
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(Constants.dbCheckForInventoryEnchantmentsTable);
			
			//if table doesnt exist
			if(!result.next()){
				//create it
				statement = getConnection().createStatement();
				statement.execute(Constants.dbCreateInventoryEnchantmentsTable);
			}
		}catch(Exception e){
			//error occurred
			getLogger().info(Constants.errorCouldNotCreateInventoryEnchantmentsTable);
			e.printStackTrace();
		}
	}
	
	//ensure that the claims table exists
	private void ensureClaimsTable(){
		try{
			//create statment to check for tables existence
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(Constants.dbCheckForClaimsTable);
			
			//if table doesnt exist
			if(!result.next()){
				//create table
				statement = getConnection().createStatement();
				statement.execute(Constants.dbCreateClaimsTable);
			}
		}catch(Exception e){
			//an error occurred
			getLogger().info(Constants.errorCouldNotCreateClaimsTable);
			e.printStackTrace();
		}
	}
	
	//clear out the player's stored inventory enchantments
	private void clearPlayerInventoryEnchantments(String thePlayer){
		//make sure the table is there
		ensureInventoryEnchantmentsTable();
		try{
			//clear out the table for the enchantments table for the player
			Statement statement = getConnection().createStatement();
			statement.execute(String.format(Constants.dbClearPlayerInventoryEnchantments,thePlayer.replaceAll("'","''")));
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotClearPlayerInventoryEnchantments);
			e.printStackTrace();
		}
	}
	
	//clear out the player's stored inventory
	public void clearPlayerInventory(String thePlayer){
		//make sure the table is there
		ensureInventoryTable();
		try{
			//create statemetn for clearing inventory
			Statement statement = getConnection().createStatement();
			statement.execute(String.format(Constants.dbClearPlayerInventory,thePlayer.replaceAll("'","''")));
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotClearPlayerInventory);
			e.printStackTrace();
		}
		clearPlayerInventoryEnchantments(thePlayer);
	}
	
	//set the player's number of claims
	public Boolean setPlayerClaims(String thePlayer, Integer theClaims){
		//make sure table exists
		ensureClaimsTable();
		try{
			//two statements, delete entry, then insert entry
			Statement statement = getConnection().createStatement();
			statement.execute(String.format(Constants.dbDeletePlayerClaims,thePlayer.replaceAll("'","''")));
			statement.execute(String.format(Constants.dbInsertPlayerClaims,theClaims,thePlayer.replaceAll("'","''")));
			return true;
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotSetPlayerClaims);
			e.printStackTrace();
		}
		return false;
	}
	
	//retrieve player's number of claims
	public Integer retrievePlayerClaims(String thePlayer){
		//make sure table exists
		ensureClaimsTable();
		try{
			//find claim entry for player
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(String.format(Constants.dbRetrievePlayerClaims,thePlayer.replaceAll("'","''")));
			//if found
			if(result.next()){
				//grab from table
				return result.getInt(Constants.fieldClaims);
			}else{
				//not found, so zero claims
				return Constants.claimsZero;
			}
		}catch(Exception e){
			//exception
			e.printStackTrace();
			return Constants.claimsZero;
		}
	}
	
	//calculate the coverage cost for the player
	public Integer getPlayerCoverageCost(Player thePlayer){
		//grab out the configured minimums, maximums, and acceleration
		Integer theMinimum = getPlugin().getPluginConfiguration().getMinimum();
		Integer theMaximum = getPlugin().getPluginConfiguration().getMaximum();
		Integer theAcceleration = getPlugin().getPluginConfiguration().getAcceleration();
		
		//retrieve the player's claims
		Integer theClaims = retrievePlayerClaims(thePlayer.getName());
		
		//apply "the formula"
		Integer theCost = theMinimum + theClaims * theAcceleration;
		
		//clamp to minimum and maximum
		if(theCost>theMaximum) theCost = theMaximum;
		if(theCost<theMinimum) theCost = theMinimum;
		
		//clamp to a minimum of zero no matter what
		if(theCost<Constants.costZero) theCost = Constants.costZero;
		return theCost;
	}
	
	private boolean isPlayerCoveredDefaultPaySource(Player thePlayer){
		//grab the material
		Material theMaterial = getPlugin().getPluginConfiguration().getMaterial();
		
		//calculate the cost
		Integer theCost = getPlayerCoverageCost(thePlayer);
		
		//if no cost, coverage is assured
		if(theCost<=Constants.costZero) return true;
		
		//grab the player's inventory
		PlayerInventory inventory = thePlayer.getInventory();
		ItemStack[] itemStacks = inventory.getContents();
		
		//go through the inventory
		for(Integer index=Constants.indexZero;index<itemStacks.length;++index){
			//examine the inventory stack
			ItemStack itemStack = itemStacks[index];
			if(itemStack!=null){
				//does the material match?
				if(itemStack.getTypeId()==theMaterial.getId()){
					//decrease the cost
					theCost -= itemStack.getAmount();
					//if the cost has been taken care of, then covered!
					if(theCost<=Constants.costZero) return true;
				}
			}
		}
		//cost not met, not covered!
		return false;
	}
	private boolean isPlayerCoveredVaultPaySource(Player thePlayer){
		Economy economy = getPlugin().getEconomy();
		if(economy==null){
			return false;
		}
		//calculate the cost
		Integer theCost = getPlayerCoverageCost(thePlayer);
		return (economy.has(thePlayer.getName(), theCost));
	}
	
	//find out if the player is covered
	public Boolean isPlayerCovered(Player thePlayer){
		switch(getPlugin().getPluginConfiguration().getPaySource())
		{
		case VAULT:
			return isPlayerCoveredVaultPaySource(thePlayer);
		default:
			return isPlayerCoveredDefaultPaySource(thePlayer);
		}
	}
	
	//insert an entry in player inventory
	private void insertPlayerInventory(String thePlayerName, Integer theSlot, Integer theType, Integer theAmount, Byte theData, Short theDurability){
		//make sure table is there
		ensureInventoryTable();
		try{
			//create entry in table
			Statement statement = getConnection().createStatement();
			statement.execute(String.format(Constants.dbInsertPlayerInventory,thePlayerName.replaceAll("'","''"),theSlot,theType,theAmount,theData,theDurability));
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotInsertPlayerInventory);
			e.printStackTrace();
		}
	}
	
	//insert an entry in the enchantments table
	private void insertPlayerInventoryEnchantment(String thePlayerName, Integer theSlot, Enchantment theEnchantment, Integer theLevel){
		//make sure table is there
		ensureInventoryEnchantmentsTable();
		try{
			//create entry in table
			Statement statement = getConnection().createStatement();
			statement.execute(String.format(Constants.dbInsertPlayerInventoryEnchantments,thePlayerName.replaceAll("'","''"),theSlot,theEnchantment.getId(),theLevel));
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotInsertPlayerInventoryEnchantment);
			e.printStackTrace();
		}
	}
	
	//persist the inventory
	public void persistInventory(Player thePlayer){
		//grab player name
		String thePlayerName = thePlayer.getName();
		//clear the player inventory
		clearPlayerInventory(thePlayerName);
		//grab configured material
		Material theMaterial = getPlugin().getPluginConfiguration().getMaterial();
		//calculate cost for player
		Integer theCost = getPlayerCoverageCost(thePlayer);
		FilterState theFilterState = getPlugin().getPluginConfiguration().getFilterState();
		Set<Material> theFilterMaterials = getPlugin().getPluginConfiguration().getFilterMaterials();
		
		switch(getPlugin().getPluginConfiguration().getPaySource()){
		case VAULT:
			getPlugin().getEconomy().withdrawPlayer(thePlayer.getName(), theCost);
			theCost = 0;
			break;
		default:
			break;
		}
		
		//grab inventory
		PlayerInventory inventory = thePlayer.getInventory();
		//loop through inventory
		ItemStack[] itemStacks = inventory.getContents();
		for(Integer index=0;index<itemStacks.length;++index){
			//examine individual stack
			ItemStack itemStack = itemStacks[index];
			if(itemStack!=null){
				//grab information from item stack
				Integer itemType = itemStack.getData().getItemTypeId();
				Integer amount = itemStack.getAmount();
				//is this the cost item?
				if(theCost>0 && itemType==theMaterial.getId()){
					//deduct amount from cost if any cost remains
					if(theCost>amount){
						theCost -= amount;
						amount = 0;
					}else{
						amount-=theCost;
						theCost=0;
					}
				}
				//if anything is left...
				if(amount>0 && theFilterState.checkPersistence(itemStack.getData().getItemType(), theFilterMaterials)){
					//grab other data
					Short durability = itemStack.getDurability();
					Byte data = itemStack.getData().getData();
					//insert the item into persisted inventory
					insertPlayerInventory(thePlayerName,index,itemType,amount,data,durability);
					//grab enchantments
					Map<Enchantment,Integer> enchantments = itemStack.getEnchantments();
					//loop through them
					Iterator<Entry<Enchantment,Integer>> iter = enchantments.entrySet().iterator();
					while(iter.hasNext()){
						//persist enchantments to table
						Entry<Enchantment,Integer> entry = iter.next();
						insertPlayerInventoryEnchantment(thePlayerName,index,entry.getKey(),entry.getValue());
					}
				}
			}
		}
		//repeat the same process for the armor contents, which is not in main inventory
		//but use negative inventory indices
		itemStacks = inventory.getArmorContents();
		for(Integer index=0;index<itemStacks.length;++index){
			Integer slot = -index-1;
			ItemStack itemStack = itemStacks[index];
			if(itemStack!=null){
				Integer itemType = itemStack.getData().getItemTypeId();
				Integer amount = itemStack.getAmount();
				if(theCost>0 && itemType==theMaterial.getId()){
					if(theCost>amount){
						theCost -= amount;
						amount = 0;
					}else{
						amount-=theCost;
						theCost=0;
					}
				}
				if(amount>0 && theFilterState.checkPersistence(itemStack.getData().getItemType(), theFilterMaterials)){
					Short durability = itemStack.getDurability();
					Byte data = itemStack.getData().getData();
					insertPlayerInventory(thePlayerName,slot,itemType,amount,data,durability);
					Map<Enchantment,Integer> enchantments = itemStack.getEnchantments();
					Iterator<Entry<Enchantment,Integer>> iter = enchantments.entrySet().iterator();
					while(iter.hasNext()){
						Entry<Enchantment,Integer> entry = iter.next();
						insertPlayerInventoryEnchantment(thePlayerName,slot,entry.getKey(),entry.getValue());
					}
				}
			}
		}
	}
	
	//retrieve enchantments
	private Map<Enchantment,Integer> retrievePlayerInventoryEnchantments(String thePlayerName, Integer theSlot){
		Map<Enchantment, Integer> theResult = null;
		//ensure tables
		ensureInventoryEnchantmentsTable();
		try{
			//call retrieve from database
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(String.format(Constants.dbRetrievePlayerInventoryEnchantments,thePlayerName.replaceAll("'","''"),theSlot));
			//loop through entries
			while(result.next()){
				//create a new enchantment
				Enchantment theEnchantment = Enchantment.getById(result.getInt(Constants.fieldEnchantment));
				Integer theLevel = result.getInt(Constants.fieldLevel);
				if(theResult==null){
					theResult = new HashMap<Enchantment, Integer>();
				}
				//add to map of enchantemtns
				theResult.put(theEnchantment, theLevel);
			}
		}catch(Exception e){
			//error occurred
			getLogger().severe(Constants.errorCouldNotRetrievePlayerInventoryEnchantments);
			e.printStackTrace();
			theResult = null;
		}
		//return a map of enchantments
		return theResult;
	}
	
	//restore inventory
	public void restoreInventory(Player thePlayer){
		//grab player name and inventory
		String thePlayerName = thePlayer.getName();
		PlayerInventory theInventory = thePlayer.getInventory();
		//create a contents
		ItemStack[] theNewContents = new ItemStack[theInventory.getSize()];
		ItemStack[] theNewArmorContents = new ItemStack[theInventory.getArmorContents().length];
		//make sure table exists
		ensureInventoryTable();
		try{
			//retrieve inventory list
			Statement statement = getConnection().createStatement();
			ResultSet result = statement.executeQuery(String.format(Constants.dbRetrievePlayerInventory,thePlayerName.replaceAll("'","''")));
			//found flag
			Boolean found = false;
			//loop through results
			while(result.next()){
				//set found flag
				found = true;
				//grab information from row
				Integer theSlot = result.getInt(Constants.fieldSlot);
				Integer theItemType = result.getInt(Constants.fieldType);
				Integer theAmount = result.getInt(Constants.fieldAmount);
				Integer theDurability = result.getInt(Constants.fieldDurability);
				
				//create an item stack based on information from table
				ItemStack itemStack = new ItemStack(theItemType,theAmount,theDurability.shortValue());
				
				//grab the enchantments for the inventory slot
				Map<Enchantment, Integer> enchantments = retrievePlayerInventoryEnchantments(thePlayerName.replaceAll("'","''"),theSlot);
				
				//apply enchantments if they exist
				if(enchantments!=null){
					itemStack.addEnchantments(enchantments);
				}
				
				//negative slot?
				if(theSlot<0){
					//belongs to armor
					theSlot = (-theSlot)-1;
					theNewArmorContents[theSlot]=itemStack;
				}else{
					//positive slot belongs to main inventory
					theNewContents[theSlot]=itemStack;
				}
			}
			//if found flag set
			if(found){
				//set contents of inventory and armor
				theInventory.setContents(theNewContents);
				theInventory.setArmorContents(theNewArmorContents);
				clearPlayerInventory(thePlayer.getName());
			}
		}catch(Exception e){
			//an error occurred
			e.printStackTrace();
		}
		
	}
	
	
	//constructor
	public DataProvider(SpawnSurance thePlugin){
		plugin = thePlugin;
	}

	public void filterDrops(List<ItemStack> theDrops) {
		FilterState theFilterState = getPlugin().getPluginConfiguration().getFilterState();
		Set<Material> theFilterMaterials = getPlugin().getPluginConfiguration().getFilterMaterials();
		List<ItemStack> removeThese = new ArrayList<ItemStack>();
		Iterator<ItemStack> iterator = theDrops.iterator();
		while(iterator.hasNext()){
			ItemStack theStack = iterator.next();
			if(!theFilterState.checkDrop(theStack.getData().getItemType(), theFilterMaterials)){
				removeThese.add(theStack);
			}
		}
		iterator = removeThese.iterator();
		while(iterator.hasNext()){
			theDrops.remove(iterator.next());
		}
	}

}
